﻿using Mapster;
using Microsoft.EntityFrameworkCore;
using MQTTnet;
using MQTTnet.Protocol;
using MQTTnet.Server;
using Newtonsoft.Json;
using SmartAgricultureService.Models;
using SmartAgricultureService.Models.Enums;
using SmartAgricultureService.Models.Tables;
using SmartAgricultureService.Tables;
using System.Text;

namespace SmartAgricultureService.Core.Mqtt
{
    public sealed class MqttHostService : IHostedService
    {
        private readonly DatabaseContext _database;
        public MqttHostService()
        {
            var dbOptionsBuilder = new DbContextOptionsBuilder<DatabaseContext>();
            var serverVersion = ServerVersion.AutoDetect(AppConfiguration.ConnectionString);
            dbOptionsBuilder.UseMySql(AppConfiguration.ConnectionString, serverVersion);
            _database = new DatabaseContext(dbOptionsBuilder.Options);
            var mqttOptionsBuilder = new MqttServerOptionsBuilder();
            mqttOptionsBuilder.WithDefaultEndpoint();
            mqttOptionsBuilder.WithDefaultEndpointPort(AppConfiguration.Mqtt.Port);
            var options = mqttOptionsBuilder.Build();
            MqttService.Server = new MqttFactory().CreateMqttServer(options);
            Logger.Instance.LogInfo($"init MqttHostService at [{AppConfiguration.Mqtt.Port}]...");
        }
        public async Task StartAsync(CancellationToken cancellationToken)
        {
            try
            {
                MqttService.Server.InterceptingPublishAsync += OnInterceptingPublishAsync;
                MqttService.Server.ValidatingConnectionAsync += OnValidatingConnectionAsync;
                await MqttService.Server.StartAsync();
                HeartbeatQueue.Instance.StartListen();
                Logger.Instance.LogInfo("started MqttHostService...");
            }
            catch (Exception ex)
            {
                Logger.Instance.LogError("StartAsync", ex);
            }
        }
        private static Task OnValidatingConnectionAsync(ValidatingConnectionEventArgs arg)
        {
            arg.ReasonCode = (((arg.UserName ?? string.Empty) != AppConfiguration.Mqtt.User) || ((arg.Password ?? string.Empty) != AppConfiguration.Mqtt.Passwd)) ? MqttConnectReasonCode.Banned : MqttConnectReasonCode.Success;
            return Task.CompletedTask;
        }
        private async Task OnInterceptingPublishAsync(InterceptingPublishEventArgs arg)
        {
            try
            {
                if (!string.Equals(arg.ClientId, MqttService.ServerClientId, StringComparison.CurrentCultureIgnoreCase) && string.Equals(arg.ClientId, AppConfiguration.Mqtt.ClientId, StringComparison.CurrentCultureIgnoreCase))
                {
                    var payload = Encoding.UTF8.GetString(arg.ApplicationMessage.PayloadSegment);
                    try
                    {
                        var mqttMsg = JsonConvert.DeserializeObject<MqttData>(payload);
                        if (mqttMsg?.Data != null)
                        {
                            var device = await _database.Devices.Include(o => o.Area).AsNoTracking().ProjectToType<DeviceInfo>(DeviceInfo.GetAdapterConfig()).FirstOrDefaultAsync(x => x.Mac == mqttMsg.Data.Mac);
                            if (device != null)
                            {
                                await MqttService.PublishData(MqttData.Create(device.AreaCode, new MqttDataDetail
                                {
                                    Type = MqttType.Online,
                                    Devices = [device]
                                }));
                                HeartbeatQueue.Instance.Update(mqttMsg.DeviceId);
                            }
                            else
                            {
                                if (await _database.Devices.AsNoTracking().AnyAsync(x => x.Id == mqttMsg.DeviceId))
                                {
                                    HeartbeatQueue.Instance.Update(mqttMsg.DeviceId);
                                    switch (mqttMsg.Data.Type)
                                    {
                                        case MqttType.Data:
                                            {
                                                var entry = new UploadEntry
                                                {
                                                    CreateDate = DateTime.Now,
                                                    DeviceId = mqttMsg.DeviceId.Trim(),
                                                    Id = Guid.NewGuid().ToString("N"),
                                                    Humidity = mqttMsg.Data.Humidity ?? 0,
                                                    Temperature = mqttMsg.Data.Temperature ?? 0,
                                                    Illumination = mqttMsg.Data.Illumination ?? 0
                                                };
                                                await _database.Uploads.AddAsync(entry);
                                                break;
                                            }
                                        case MqttType.Irrigate:
                                        case MqttType.IrrigateInterval:
                                        case MqttType.IrrigateThreshold:
                                        case MqttType.IrrigateStop:
                                            {
                                                var entry = new LogEntry
                                                {
                                                    CreateDate = DateTime.Now,
                                                    DeviceId = mqttMsg.DeviceId,
                                                    Id = Guid.NewGuid().ToString("N"),
                                                    Message = $"设备上传下发的相应数据，${mqttMsg.Data.Type}=${mqttMsg.Data.Response}"
                                                };
                                                await _database.Logs.AddAsync(entry);
                                                break;
                                            }
                                        case MqttType.Report:
                                            {
                                                var entry = new LogEntry
                                                {
                                                    DeviceId = mqttMsg.DeviceId,
                                                    CreateDate = DateTime.Now,
                                                    Id = Guid.NewGuid().ToString("N"),
                                                    Message = $"未受支持的消息类型[{mqttMsg}]"
                                                };
                                                await _database.Logs.AddAsync(entry);
                                                break;
                                            }
                                        case MqttType.Online:
                                        default:
                                            break;
                                    }
                                    await _database.SaveChangesAsync();
                                }
                                else
                                {
                                    var log = new LogEntry
                                    {
                                        DeviceId = null,
                                        CreateDate = DateTime.Now,
                                        Id = Guid.NewGuid().ToString("N"),
                                        Message = $"未验证的设备报文[{mqttMsg}]"
                                    };
                                    await _database.Logs.AddAsync(log);
                                    await _database.SaveChangesAsync();
                                }
                            }
                        }
                        else
                        {
                            var log = new LogEntry
                            {
                                DeviceId = null,
                                CreateDate = DateTime.Now,
                                Id = Guid.NewGuid().ToString("N"),
                                Message = $"未标识的设备报文[{mqttMsg}]"
                            };
                            await _database.Logs.AddAsync(log);
                            await _database.SaveChangesAsync();
                        }
                    }
                    catch (Exception ex)
                    {
                        Logger.Instance.LogInfo($"ClientMessageReceivedAsync:【{payload}】");
                        Logger.Instance.LogError("ClientMessageReceivedAsync", ex);
                    }
                }
            }
            catch (Exception ex)
            {
                Logger.Instance.LogError("OnInterceptingPublishAsync", ex);
            }
        }
        public Task StopAsync(CancellationToken _) => Task.CompletedTask;
    }
}